<?php
namespace App\Pay\Gateway\Qqpay;

use App\Pay\Config\Config;
use App\Pay\Exceptions\GatewayException;
use App\Pay\Exceptions\InvalidArgumentException;
use GuzzleHttp\Client;

class Request
{

    /**
     * @var Config
     */
    private $config;

    /**
     * @var self
     */
    private static $instance;

    /**
     * @var string
     */
    protected $url;

    /**
     * @var Client
     */
    protected $client;

    /**
     * @var array
     */
    protected $result;

    public static function instance()
    {
        if (is_null(self::$instance)) {
            return self::$instance = new static();
        }
        return self::$instance;
    }

    /**
     * @param Config $config
     */
    public function setConfig(Config $config)
    {
        $this->config = $config;
        $this->url = 'https://qpay.qq.com/';
        $this->client = new Client(['base_uri' => $this->url]);
    }

    /**
     * @return Config
     */
    public function config()
    {
        return $this->config;
    }

    /**
     * @return string
     */
    public function baseUrl()
    {
        return $this->url;
    }

    /**
     * @param $api
     * @param $params
     * @return mixed
     */
    public static function api($api, $params)
    {
        return self::$instance->post($api, $params);
    }

    /**
     * @param $api
     * @param $params
     * @return mixed
     * @throws InvalidArgumentException
     */
    public function post($api, $params)
    {
        $xml = self::arrToXml($params);
        $response = $this->client->post($api, ['body' => $xml]);
        $result = self::xmlToArr($response->getBody()->getContents());
        if ('SUCCESS' === $result['return_code'] && 'SUCCESS' === $result['result_code']) {
            return $result;
        }
        throw new GatewayException($result['err_code_des'] ?? '');
    }

    /**
     * @param array $data
     * @return string
     * @throws InvalidArgumentException
     */
    public static function arrToXml(array $data): string
    {
        if (!is_array($data) || count($data) <= 0) {
            throw new InvalidArgumentException('Convert To Xml Error! Invalid Array!');
        }
        $xml = '<xml>';
        foreach ($data as $key => $val) {
            $xml .= is_numeric($val) ? '<'.$key.'>'.$val.'</'.$key.'>' :
                '<'.$key.'><![CDATA['.$val.']]></'.$key.'>';
        }
        $xml .= '</xml>';

        return $xml;
    }

    /**
     * @param string $xml
     * @return array
     * @throws InvalidArgumentException
     */
    public static function xmlToArr(string $xml): array
    {
        if (!$xml) {
            throw new InvalidArgumentException('Convert To Array Error! Invalid Xml!');
        }

        libxml_disable_entity_loader(true);

        return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA), JSON_UNESCAPED_UNICODE), true);
    }

    public static function sign($payload)
    {
        ksort($payload);
        foreach ($payload as $k => $v) {
            if (!$v || $k === 'sign') {
                unset($payload[$k]);
            }
        }
        return strtoupper(md5(self::getSignContent($payload) . '&key='. Request::instance()->config()->get('mch_key')));
    }

    public static function getSignContent($params)
    {
        return urldecode(http_build_query($params));
    }
}
